package cn.itcast.ssh.service.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipInputStream;

import org.activiti.engine.FormService;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.form.TaskFormData;
import org.activiti.engine.history.HistoricVariableInstance;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.identity.Authentication;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.pvm.PvmTransition;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.impl.pvm.process.ProcessDefinitionImpl;
import org.activiti.engine.impl.pvm.process.TransitionImpl;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Comment;
import org.activiti.engine.task.Task;
import org.apache.commons.lang3.StringUtils;

import cn.itcast.ssh.dao.ILeaveBillDao;
import cn.itcast.ssh.domain.Employee;
import cn.itcast.ssh.domain.LeaveBill;
import cn.itcast.ssh.service.IWorkflowService;
import cn.itcast.ssh.utils.SessionContext;
import cn.itcast.ssh.web.form.WorkflowBean;

public class WorkflowServiceImpl implements IWorkflowService {
	/**请假申请Dao*/
	private ILeaveBillDao leaveBillDao;
	
	private RepositoryService repositoryService;
	
	private RuntimeService runtimeService;
	
	private TaskService taskService;
	
	private FormService formService;
	
	private HistoryService historyService;
	
	public void setLeaveBillDao(ILeaveBillDao leaveBillDao) {
		this.leaveBillDao = leaveBillDao;
	}

	public void setHistoryService(HistoryService historyService) {
		this.historyService = historyService;
	}
	
	public void setFormService(FormService formService) {
		this.formService = formService;
	}
	
	public void setRuntimeService(RuntimeService runtimeService) {
		this.runtimeService = runtimeService;
	}
	public void setTaskService(TaskService taskService) {
		this.taskService = taskService;
	}

	public void setRepositoryService(RepositoryService repositoryService) {
		this.repositoryService = repositoryService;
	}

	@Override
	public void saveNewDeploye(File file, String filename) {
		//将File类型的文件转化为zip文件流
		try {
			ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(file));
			repositoryService.createDeployment()//创建部署对象
								.name(filename)//添加部署名称
								.addZipInputStream(zipInputStream)
								.deploy();//完成部署
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public List<Deployment> findDeploymentList() {
		List<Deployment> list = repositoryService.createDeploymentQuery()
							.orderByDeploymenTime().asc()
							.list();
		return list;
	}

	@Override
	public List<ProcessDefinition> findProcessDefinitionList() {
		List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery()
							.orderByProcessDefinitionKey().asc()
							.list();
		return list;
	}

	@Override
	public InputStream findImageInputStream(String deploymentId, String imageName) {
		return repositoryService.getResourceAsStream(deploymentId, imageName);
	}

	@Override
	public void deleteProcessDefinitionByDeploymentId(String deploymentId) {
		repositoryService.deleteDeployment(deploymentId, true);;
	}

	@Override
	public void saveStartProcess(WorkflowBean workflowBean) {
		//1.获取请假单ID,使用请假单ID,查询请假单的对象leaveBill
		Long id = workflowBean.getId();
		LeaveBill leaveBill = leaveBillDao.findLeaveBillById(id);
		//2.更新请假单的状态从0变成1(初始录入 --- > 审核中)
		leaveBill.setState(1);
		//3.使用当前对象获取流程定义的key(在新建bpmn的时候,就设定了bpmn的process的ID属性和实体class的类名一致,有了这个规则之后,就可以通过class的类名直接获取process的key值)
		String key = leaveBill.getClass().getSimpleName();
		
		/**
		 * 4.从session中获取当前任务的办理人,使用流程变量指定下一个任务的办理人
		 * inputUser是流程变量的名称
		  *   获取的办理人是流程变量的值
		 */
		Map<String,Object> variables = new HashMap<>();
		variables.put("inputUser", SessionContext.get().getName());//指定任务办理人
		/**
		 *5.(1)使用流程变量设置字符串(格式leaveBill.id的形式),通过设置,让启动的流程(流程实例)关联业务 
		 *  (2)使用正在执行对象表中的一个字段(business_key_),让启动的流程实例管理业务
		 */
		//格式:leaveBill.id的形式(使用流程变量)
		String objId = key+"."+id;
		variables.put("objId", objId);
		//6.使用流程变量的key,启动流程实例,同时设置流程变量,同时向正在执行的表的对象中的business_key_添加业务数据
		runtimeService.startProcessInstanceByKey(key,objId, variables);
	}

	@Override
	public List<Task> findTaskListByName(String name) {
		List<Task> list = taskService.createTaskQuery()
					.taskAssignee(name)//指定个人任务查询
					.orderByTaskCreateTime().asc()
					.list();
		return list;
	}

	@Override
	public String findTaskFormKeyByTaskId(String taskId) {
		TaskFormData formData = formService.getTaskFormData(taskId);
		//获取form key的值
		String formKey = formData.getFormKey();
		return formKey;
	}

	@Override
	public LeaveBill findLeaveBillByTaskId(String taskId) {
		//1.使用任务ID,获取任务对象
		Task task = taskService.createTaskQuery()
					.taskId(taskId)
					.singleResult();
		//2.通过任务对象,获取流程实例ID
		String processInstanceId = task.getProcessInstanceId();
		//3.使用流程实例ID,查询正在执行的执行对象表,返回流程实例对象
		ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
						.processInstanceId(processInstanceId)
						.singleResult();
		//4.获取business_key_
		String businessKey = processInstance.getBusinessKey();
		LeaveBill leaveBill = null;
		if(!StringUtils.isEmpty(businessKey)) {
			//保存的格式为:processKey_.leaveBillId
			String leaveBillId = businessKey.split("\\.")[1];
			leaveBill = leaveBillDao.findLeaveBillById(Long.valueOf(leaveBillId));
		}
		return leaveBill;
	}

	@Override
	public List<String> findOutcomeListByTaskId(String taskId) {
		//outcome集合
		List<String> list = new ArrayList<>();
		//1.使用任务ID,查询任务对象
		Task task = taskService.createTaskQuery()
					.taskId(taskId)
					.singleResult();
		//2.获取流程定义ID
		String processDefinitionId = task.getProcessDefinitionId();
		//3.查询对象
		ProcessDefinitionEntity processDefinitionEntity = (ProcessDefinitionEntity)repositoryService.getProcessDefinition(processDefinitionId);
		//使用任务对象,获取流程实例ID
		String processInstanceId = task.getProcessInstanceId();
		//使用流程实例对象ID,获取正在执行对象,获取流程实例对象
		ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
						.processInstanceId(processInstanceId)
						.singleResult();
		//获取当前的活动节点ID(就是每一个userTask节点)
		String activityId = processInstance.getActivityId();
		//4.获取当前的活动
		ActivityImpl activityImpl = processDefinitionEntity.findActivity(activityId);
		//5.获取当前活动完成之后的连线的名称(比如当前的任务节点是userTask1,然后下一个任务节点是userTask2,两者之间有一条sequenceFlow,那么集合为:[(usertask1)--flow2-->(usertask2)])
		List<PvmTransition> pvmList = activityImpl.getOutgoingTransitions();
		if(pvmList != null && pvmList.size() > 0) {
			for(PvmTransition pvm:pvmList) {
				String name = (String)pvm.getProperty("name");
				if(!StringUtils.isEmpty(name)) {
					list.add(name);
				}else {
					list.add("默认提交");
				}
			}
		}
		return list;
	}

	@Override
	public void saveSubmitTask(WorkflowBean workflowBean) {
		//获取任务ID
		String taskId = workflowBean.getTaskId();
		//获取连接线名称
		String outcome = workflowBean.getOutcome();
		//批注信息
		String comment = workflowBean.getComment();
		//获取请假单ID
		Long id = workflowBean.getId();
		/**
		 * 在完成任务之前,添加一个批注信息,向act_hi_commnet添加数据,用于记录对当前申请人的审核信息
		 */
		//使用任务ID,查询任务对象,获取流程实例ID
		Task task = taskService.createTaskQuery()
					.taskId(taskId)
					.singleResult();
		//获取流程实例ID
		String processInstanceId = task.getProcessInstanceId();
		//添加批注
		/**
		 * 添加批注的时候,由于底层代码使用String userId = Authentication.getAuthenticatedUserId();这个ID不是我们需要的userId值
		 * 所以需要从session中获取当前登录用户,作为该任务的办理人,存储在对应act_hi_commnet的user_id_字段
		 */
		Authentication.setAuthenticatedUserId(SessionContext.get().getName());
		taskService.addComment(taskId, processInstanceId, comment);
		
		/**
		 * 1.如果连接线名称为"默认提交",那么就表示单线,此时不需要设置变量名称来控制下个任务的执行人
		 *在完成任务之前,设置流程变量,按照连接线的名称,去完成任务,指定任务走向
		 *流程变量名称:outcome
		 *流程变量的值:连接线的名称
		 */
		Map<String,Object> variables = new HashMap<>();
		if(outcome != null && !outcome.equals("默认提交")) {
			variables.put("outcome", outcome);//通过变量指定连线走向,来指定下一个任务的执行人assignee
		}
		
		Employee employee = SessionContext.get();
		variables.put("username", employee.getName());
		//2.使用任务ID,完成当前个人任务,同时设置流程变量
		taskService.complete(taskId, variables);
		
		//判断是否终审,即流程是否已经结束
		ProcessInstance pi = runtimeService.createProcessInstanceQuery()
						.processInstanceId(processInstanceId)
						.singleResult();
		if(pi == null) {
			//流程结束了
			LeaveBill leaveBill = leaveBillDao.findLeaveBillById(id);
			leaveBill.setState(2);
		}
	}

	@Override
	public List<Comment> findCommentByTaskId(String taskId) {
		List<Comment> list = new ArrayList<>();
		//使用当前任务ID,获取所有历史任务
		Task task = taskService.createTaskQuery()
					.taskId(taskId)
					.singleResult();
		//获取流程实例ID
		String processInstanceId = task.getProcessInstanceId();
		//方式1.通过任务ID获取批注根据流程实例查找所有的历史任务
		/*List<HistoricTaskInstance> htiList = historyService.createHistoricTaskInstanceQuery()
						.processInstanceId(processInstanceId)
						.list();
		
		//遍历所有任务,查询对应批注
		for(HistoricTaskInstance hti:htiList) {
			//任务ID
			String hTaskId = hti.getId();
			//获取批注信息
			List<Comment> taskComments = taskService.getTaskComments(hTaskId);
			list.addAll(taskComments);
		}*/
		//方法2:通过processInstanceId查询批注
		List<Comment> comments = taskService.getProcessInstanceComments(processInstanceId);
		list.addAll(comments);//和方法1是一样的
		return list;
	}

	@Override
	public List<Comment> findCommentByLeaveBillId(Long id) {
		//1.使用请假单ID,查询请假对象
		LeaveBill leaveBill = leaveBillDao.findLeaveBillById(id);
		//获取对象名称
		String objectName = leaveBill.getClass().getSimpleName();
		//组织流程表中的字段中的值
		String objId = objectName+"."+id;

		//获取历史流程实例ID
		/**
		 * 方法1.使用历史的流程实例查询,返回历史的流程实例对象,获取流程实例ID
		 */
//		HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery()
//						.processInstanceBusinessKey(objId)
//						.singleResult();
		
//		String hicProcessInstanceId = hpi.getId();//
		//方法2:
		HistoricVariableInstance hvi = historyService.createHistoricVariableInstanceQuery()
						.variableValueEquals("objId", objId)//通过变量名和变量值来查找唯一记录
						.singleResult();
		String hicProcessInstanceId = hvi.getProcessInstanceId();				
		//2.使用历史的流程变量查询
		List<Comment> processInstanceComments = taskService.getProcessInstanceComments(hicProcessInstanceId);
		return processInstanceComments;
	}

	@Override
	public ProcessDefinition findProcessDefinitionByTaskId(String taskId) {
		//根据任务ID,获取任务对象
		Task task = taskService.createTaskQuery()
					.taskId(taskId)
					.singleResult();
		
		//根据任务对象,获取流程定义ID
		String processDefinitionId = task.getProcessDefinitionId();
		ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()//创建流程定义查询对象,对应表act_re_prodef
							.processDefinitionId(processDefinitionId)
							.singleResult();
		
		return pd;
	}

	@Override
	public Map<String, Object> findCoordingByTask(String taskId) {
		//存放坐标
		Map<String,Object> map = new HashMap<>();
		//根据任务ID,获取任务对象
		Task task = taskService.createTaskQuery()
					.taskId(taskId)
					.singleResult();
		//获取流程定义ID
		String processDefinitionId = task.getProcessDefinitionId();
		//获取流程定义的实体对象(对应.bpmn文件中的数据)
		ProcessDefinitionEntity processDefinitionEntity = (ProcessDefinitionEntity)repositoryService.getProcessDefinition(processDefinitionId);
		
		//流程实例ID
		String processInstanceId = task.getProcessInstanceId();
		//使用流程实例ID,查询正在执行的任务,获取当前活动对应的流程实例对象
		ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
						.processInstanceId(processInstanceId)
						.singleResult();
		//获取当前活动的ID
		String activityId = processInstance.getActivityId();
		
		//获取当前活动对象
		ActivityImpl activityImpl = processDefinitionEntity.findActivity(activityId);
		 
		//获取坐标
		map.put("x", activityImpl.getX());
		map.put("y", activityImpl.getY());
		map.put("width", activityImpl.getWidth());
		map.put("height", activityImpl.getHeight());
		return map;
	}

	/**
	 * 中止流程(特权人直接审批通过等)
	 * 
	 * @param taskId
	 */
	@Override
	public void endProcess(WorkflowBean workflowBean) throws Exception {
		String taskId = workflowBean.getTaskId();
		String comment = workflowBean.getComment();
		//使用任务ID,查询任务对象,获取流程实例ID
		Task task = taskService.createTaskQuery()
				.taskId(taskId)
				.singleResult();
		//获取流程实例ID
		String processInstanceId = task.getProcessInstanceId();
		Authentication.setAuthenticatedUserId(SessionContext.get().getName());
		//添加批注
		taskService.addComment(taskId, processInstanceId, comment);
		
		ActivityImpl endActivity = findActivitiImpl(taskId, "end");
		commitProcess(taskId, null, endActivity.getId());
	}
	
	/**
	 * 根据任务ID和节点ID获取活动节点 <br>
	 * 
	 * @param taskId
	 *            任务ID
	 * @param activityId
	 *            活动节点ID <br>
	 *            如果为null或""，则默认查询当前活动节点 <br>
	 *            如果为"end"，则查询结束节点 <br>
	 * 
	 * @return
	 * @throws Exception
	 */
	private ActivityImpl findActivitiImpl(String taskId, String activityId)
			throws Exception {
		// 取得流程定义
		ProcessDefinitionEntity processDefinition = findProcessDefinitionEntityByTaskId(taskId);
 
		// 获取当前活动节点ID
		if (activityId == null) {
			activityId = findTaskById(taskId).getTaskDefinitionKey();
		}
 
		// 根据流程定义，获取该流程实例的结束节点
		if (activityId.toUpperCase().equals("END")) {
			for (ActivityImpl activityImpl : processDefinition.getActivities()) {
				List<PvmTransition> pvmTransitionList = activityImpl
						.getOutgoingTransitions();
				if (pvmTransitionList.isEmpty()) {
					return activityImpl;
				}
			}
		}
 
		// 根据节点ID，获取对应的活动节点
		ActivityImpl activityImpl = ((ProcessDefinitionImpl) processDefinition)
				.findActivity(activityId);
 
		return activityImpl;
	}
	
	/**
	 * 根据任务ID获取流程定义
	 * 
	 * @param taskId
	 *            任务ID
	 * @return
	 * @throws Exception
	 */
	private ProcessDefinitionEntity findProcessDefinitionEntityByTaskId(
			String taskId) throws Exception {
		// 取得流程定义
		ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
				.getDeployedProcessDefinition(findTaskById(taskId)
						.getProcessDefinitionId());
 
		if (processDefinition == null) {
			throw new Exception("流程定义未找到!");
		}
 
		return processDefinition;
	}
	
	/**
	 * 根据任务ID获得任务实例
	 * 
	 * @param taskId
	 *            任务ID
	 * @return
	 * @throws Exception
	 */
	private TaskEntity findTaskById(String taskId) throws Exception {
		TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(
				taskId).singleResult();
		if (task == null) {
			throw new Exception("任务实例未找到!");
		}
		return task;
	}
	
	/**
	 * @param taskId
	 *            当前任务ID
	 * @param variables
	 *            流程变量
	 * @param activityId
	 *            流程转向执行任务节点ID<br>
	 *            此参数为空，默认为提交操作
	 * @throws Exception
	 */
	private void commitProcess(String taskId, Map<String, Object> variables,
			String activityId) throws Exception {
		if (variables == null) {
			variables = new HashMap<String, Object>();
		}
		// 跳转节点为空，默认提交操作
		if (activityId == null) {
			taskService.complete(taskId, variables);
		} else {// 流程转向操作
			turnTransition(taskId, activityId, variables);
		}
	}
	
	/**
	 * 流程转向操作
	 * 
	 * @param taskId
	 *            当前任务ID
	 * @param activityId
	 *            目标节点任务ID
	 * @param variables
	 *            流程变量
	 * @throws Exception
	 */
	private void turnTransition(String taskId, String activityId,
			Map<String, Object> variables) throws Exception {
		// 当前节点
		ActivityImpl currActivity = findActivitiImpl(taskId, null);
		// 清空当前流向
		List<PvmTransition> oriPvmTransitionList = clearTransition(currActivity);
 
		// 创建新流向
		TransitionImpl newTransition = currActivity.createOutgoingTransition();
		// 目标节点
		ActivityImpl pointActivity = findActivitiImpl(taskId, activityId);
		// 设置新流向的目标节点
		newTransition.setDestination(pointActivity);
 
		// 执行转向任务
		taskService.complete(taskId, variables);
		// 删除目标节点新流入
		pointActivity.getIncomingTransitions().remove(newTransition);
 
		// 还原以前流向
		restoreTransition(currActivity, oriPvmTransitionList);
	}
	
	/**
	 * 清空指定活动节点流向
	 * 
	 * @param activityImpl
	 *            活动节点
	 * @return 节点流向集合
	 */
	private List<PvmTransition> clearTransition(ActivityImpl activityImpl) {
		// 存储当前节点所有流向临时变量
		List<PvmTransition> oriPvmTransitionList = new ArrayList<PvmTransition>();
		// 获取当前节点所有流向，存储到临时变量，然后清空
		List<PvmTransition> pvmTransitionList = activityImpl
				.getOutgoingTransitions();
		for (PvmTransition pvmTransition : pvmTransitionList) {
			oriPvmTransitionList.add(pvmTransition);
		}
		pvmTransitionList.clear();
 
		return oriPvmTransitionList;
	}
	
	/**
	 * 还原指定活动节点流向
	 * 
	 * @param activityImpl
	 *            活动节点
	 * @param oriPvmTransitionList
	 *            原有节点流向集合
	 */
	private void restoreTransition(ActivityImpl activityImpl,
			List<PvmTransition> oriPvmTransitionList) {
		// 清空现有流向
		List<PvmTransition> pvmTransitionList = activityImpl
				.getOutgoingTransitions();
		pvmTransitionList.clear();
		// 还原以前流向
		for (PvmTransition pvmTransition : oriPvmTransitionList) {
			pvmTransitionList.add(pvmTransition);
		}
	}
}
